package com.taskinception.serialization;

import android.util.Log;

import com.taskinception.connectivity.DatabaseAdapter;
import com.taskinception.model.Task;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * Class for serialization and deserialization of tasks.
 * 
 * @author Jan Svehla
 */
public class XMLParser {

    private static final String UTF_8_STRING = "utf8";
    private static final String VALUE_SEPARATOR = "_";
    private static final String TAG_TASK_STRING = "task";
    private static final String TAG_SHARED_WITH_STRING = "sharedWith";
    private static final String TAG_CHILDREN_LIST_STRING = "childrenList";

    /**
     * Deserializes XML into Task object.
     * 
     * @param xmlString
     *            string with XML
     * @return newly gained task
     */
    public static Task getTaskFromXML(String xmlString) {
	InputStream is = new ByteArrayInputStream(xmlString.getBytes());
	return getTaskFromXML(is);
    }

    /**
     * Deserializes XML into Task object.
     * 
     * @param in
     *            input stream
     * @return newly gained task
     */
    public static Task getTaskFromXML(InputStream in) {
	try {
	    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	    DocumentBuilder db = dbf.newDocumentBuilder();
	    Document doc = db.parse(in);
	    doc.getDocumentElement().normalize();

	    Node node = doc.getFirstChild();

	    Task task = getTask(node, null);
	    return task;
	} catch (ParserConfigurationException e) {
	    e.printStackTrace();
	} catch (SAXException e) {
	    e.printStackTrace();
	} catch (IOException e) {
	    e.printStackTrace();
	} finally {
	    try {
		in.close();
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	}

	return null;
    }

    /**
     * Gets Task object from document node. It iterates over list of subnodes.
     * Checkes it's name and compares it with names of attributes of task. When
     * found, it uses node's value to set attribute. If node with children tasks
     * is not empty, it recursively calls itself with node of the child. At the
     * end, new task is assigned to given parent task.
     * 
     * @param taskNode
     *            document node of task
     * @param parent
     *            parent Task where the new task will be assigned
     * @return new task
     */
    private static Task getTask(Node taskNode, Task parent) {
	Task task = new Task();
	NodeList taskList = taskNode.getChildNodes();

	String value;
	String name;
	for (int i = 0; i < taskList.getLength(); i++) {
	    Node node = taskList.item(i);
	    name = node.getNodeName();
	    if (node.getFirstChild() == null) {
		value = "";
	    } else {
		value = node.getFirstChild().getNodeValue();
	    }
	    if (value == null) {
		value = "";
	    }
//	    Log.d("getTask", name + " : " + value);

	    if (name.equals(DatabaseAdapter.TASK.ROWID)) {
	    	Log.d("getTask", name + " : " + value);
		task.setId(Integer.parseInt(value));
	    } else if (name.equals(DatabaseAdapter.TASK.OWNER)) {
	    	Log.d("getTask", name + " : " + value);
		task.setOwner(value);
	    } else if (name.equals(DatabaseAdapter.TASK.REMOTE_ID)) {
	    	Log.d("getTask", name + " : " + value);
		task.getRemoteId();
	    } else if (name.equals(DatabaseAdapter.TASK.CREATED_DATE)) {
		task.setCreationDate(Long.parseLong(value));
	    } else if (name.equals(DatabaseAdapter.TASK.LAST_MODIFIED_DATE)) {
		task.setLastModifiedDate(Long.parseLong(value));
	    } else if (name.equals(DatabaseAdapter.TASK.LAST_MODIFIER)) {
		task.setLastModifier(value);
	    } else if (name.equals(DatabaseAdapter.TASK.TITLE)) {
	    	Log.d("getTask", name + " : " + value);
		task.setTitle(value);
	    } else if (name.equals(DatabaseAdapter.TASK.BODY)) {
		task.setBody(value);
	    } else if (name.equals(DatabaseAdapter.TASK.SHARED_WITH)) {
		String ids[] = value.split(VALUE_SEPARATOR);
		ArrayList<String> list = new ArrayList<String>(
			Arrays.asList(ids));
		task.setSharedWith(list);
	    } else if (name.equals(TAG_CHILDREN_LIST_STRING)) {
		NodeList childrenList = node.getChildNodes();
		ArrayList<Task> childrenTaskList = new ArrayList<Task>();
		for (int j = 0; j < childrenList.getLength(); j++) {
		    Node childTaskNode = childrenList.item(j);
		    Task childTask = getTask(childTaskNode, task);
		    childrenTaskList.add(childTask);
		}
		task.setChildren(childrenTaskList);
	    } else {
		Log.e("XMLPARSER", "Unexpected XML tag: " + name);
	    }
	}

	if (task.getId() != 0) {
	    Log.d("XMLPARSER", "getTask: moving " + task.getTitle() + " to "
		    + parent.getId());
	    task.move(parent);
	    parent.getChildren().add(task);
	}

	return task;
    }

    /**
     * Serializes task into XML string.
     * 
     * @param task
     *            task to be serialized
     * @param rootTask
     *            root task so we can trace the path through tree to our task
     * @return String with XML representation of task
     */
    public static String getXMLfromTask(Task task, Task rootTask) {
	try {
	    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	    DocumentBuilder db = dbf.newDocumentBuilder();
	    Document doc = db.newDocument();

	    Element rootElement = getRootElement(task, rootTask, doc);
	    Element childrenListElement = doc
		    .createElement(TAG_CHILDREN_LIST_STRING);
	    childrenListElement.appendChild(getElementWithChildren(task, doc));
	    rootElement.appendChild(childrenListElement);
	    doc.appendChild(rootElement);

	    DOMSource domSource = new DOMSource(doc);
	    StringWriter writer = new StringWriter();
	    StreamResult result = new StreamResult(writer);
	    TransformerFactory tf = TransformerFactory.newInstance();
	    Transformer transformer = tf.newTransformer();
	    transformer.transform(domSource, result);

	    String resultString = writer.toString();

	    return resultString;
	} catch (ParserConfigurationException e) {
	    e.printStackTrace();
	} catch (TransformerConfigurationException e) {
	    e.printStackTrace();
	} catch (TransformerException e) {
	    e.printStackTrace();
	}

	return null;
    }

    /**
     * Gets Element (Node) of root task. For every attribute is created an
     * element. They are then filled with attributes of tasks. This method does
     * NOT include children tasks. They have to be added later. Root task can be
     * used to get path through tree to given task.
     * 
     * @param task
     *            task we want to serialize
     * @param rootTask
     *            root Task
     * @param doc
     *            Document
     * @return DOM Element of given task
     */
    private static Element getRootElement(Task task, Task rootTask, Document doc) {
	Element rootElement = doc.createElement(TAG_TASK_STRING);
	Element idElement = doc.createElement(DatabaseAdapter.TASK.ROWID);
	Element ownerElement = doc.createElement(DatabaseAdapter.TASK.OWNER);
	Element remoteIdElement = doc
		.createElement(DatabaseAdapter.TASK.REMOTE_ID);
	Element creationDateElement = doc
		.createElement(DatabaseAdapter.TASK.CREATED_DATE);
	Element lastModifiedDateElement = doc
		.createElement(DatabaseAdapter.TASK.LAST_MODIFIED_DATE);
	Element lastModifierElement = doc
		.createElement(DatabaseAdapter.TASK.LAST_MODIFIER);
	Element titleElement = doc.createElement(DatabaseAdapter.TASK.TITLE);
	Element bodyElement = doc.createElement(DatabaseAdapter.TASK.BODY);
	Element sharedWithElement = doc.createElement(TAG_SHARED_WITH_STRING);

	idElement.setTextContent(Long.toString((rootTask.getId())));
	ownerElement.setTextContent(rootTask.getOwner());
	remoteIdElement.setTextContent(rootTask.getRemoteId());
	creationDateElement.setTextContent(Long.toString(rootTask
		.getCreationDate()));
	lastModifiedDateElement.setTextContent(Long.toString(rootTask
		.getLastModifiedDate()));
	lastModifierElement.setTextContent(rootTask.getLastModifier());
	titleElement.setTextContent(rootTask.getTitle());
	bodyElement.setTextContent(rootTask.getBody());
	sharedWithElement.setTextContent("");

	rootElement.appendChild(idElement);
	rootElement.appendChild(ownerElement);
	rootElement.appendChild(remoteIdElement);
	rootElement.appendChild(creationDateElement);
	rootElement.appendChild(lastModifiedDateElement);
	rootElement.appendChild(lastModifierElement);
	rootElement.appendChild(titleElement);
	rootElement.appendChild(bodyElement);
	rootElement.appendChild(sharedWithElement);

	return rootElement;
    }

    /**
     * Gets Element (Node) of given task. For every attribute is created an
     * element. They are then filled with attributes of tasks. If task has
     * subtasks, method is called recursively and returned Element is attached
     * to "childrenList" Element.
     * 
     * @param task
     *            task we want to serialize
     * @param doc
     *            Document
     * @return DOM Element of given task
     */
    private static Element getElementWithChildren(Task task, Document doc) {
	Element rootElement = doc.createElement(TAG_TASK_STRING);
	Element idElement = doc.createElement(DatabaseAdapter.TASK.ROWID);
	Element ownerElement = doc.createElement(DatabaseAdapter.TASK.OWNER);
	Element remoteIdElement = doc
		.createElement(DatabaseAdapter.TASK.REMOTE_ID);
	Element creationDateElement = doc
		.createElement(DatabaseAdapter.TASK.CREATED_DATE);
	Element lastModifiedDateElement = doc
		.createElement(DatabaseAdapter.TASK.LAST_MODIFIED_DATE);
	Element lastModifierElement = doc
		.createElement(DatabaseAdapter.TASK.LAST_MODIFIER);
	Element titleElement = doc.createElement(DatabaseAdapter.TASK.TITLE);
	Element bodyElement = doc.createElement(DatabaseAdapter.TASK.BODY);
	Element sharedWithElement = doc.createElement(TAG_SHARED_WITH_STRING);
	Element childrenListElement = doc
		.createElement(TAG_CHILDREN_LIST_STRING);

	idElement.setTextContent(Long.toString((task.getId())));
	ownerElement.setTextContent(task.getOwner());
	remoteIdElement.setTextContent(task.getRemoteId());
	creationDateElement
		.setTextContent(Long.toString(task.getCreationDate()));
	lastModifiedDateElement.setTextContent(Long.toString(task
		.getLastModifiedDate()));
	lastModifierElement.setTextContent(task.getLastModifier());
	titleElement.setTextContent(task.getTitle());
	bodyElement.setTextContent(task.getBody());

	String sharedWithString = "";
	for (String user : task.getSharedWith()) {
	    sharedWithString += user;
	    sharedWithString += VALUE_SEPARATOR;
	}
	if (!sharedWithString.equals(""))
	    sharedWithString = sharedWithString.substring(0,
		    sharedWithString.length() - 1);
	sharedWithElement.setTextContent(sharedWithString);

	for (Task child : task.getChildren()) {
	    Element childElement = getElementWithChildren(child, doc);
	    childrenListElement.appendChild(childElement);
	}

	rootElement.appendChild(idElement);
	rootElement.appendChild(ownerElement);
	rootElement.appendChild(remoteIdElement);
	rootElement.appendChild(creationDateElement);
	rootElement.appendChild(lastModifiedDateElement);
	rootElement.appendChild(lastModifierElement);
	rootElement.appendChild(titleElement);
	rootElement.appendChild(bodyElement);
	rootElement.appendChild(sharedWithElement);
	rootElement.appendChild(childrenListElement);

	return rootElement;
    }

}
